﻿#region File and License Information
/*
<File>
	<License>
		Copyright © 2009 - 2017, Daniel Vaughan. All rights reserved.
		This file is part of Calcium (http://calciumsdk.net), 
		which is released under the MIT License.
		See file /Documentation/License.txt for details.
	</License>
	<CreationDate>2010-11-20 13:28:27Z</CreationDate>
</File>
*/
#endregion

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media;

using Outcoder.Collections;
using Outcoder.UI.Xaml.Controls.Design;

using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace Outcoder.UI.Xaml.Controls
{
    [ContentProperty("Buttons")]
    public class AppBar : ItemsControl, IApplicationBar
    {
        readonly ApplicationBar applicationBar = new ApplicationBar();
        bool loaded;

        public AppBar()
        {
            Loaded += HandleLoaded;
	        Unloaded += HandleUnloaded;
		}

	    void HandleLoaded(object sender, RoutedEventArgs e)
        {
            if (loaded)
            {
                return;
            }

            if (AutoAttach)
            {
                Attach();
            }

            loaded = true;
        }

        public void Attach()
        {
            var page = this.GetVisualAncestors<PhoneApplicationPage>()
                                                .FirstOrDefault();

	        bool inPivotOrPano = false;
            var pivot = this.GetVisualAncestors<Pivot>().FirstOrDefault();
            if (pivot != null)
            {
	            inPivotOrPano = true;

                pivot.SelectionChanged -= pivot_SelectionChanged;
                pivot.SelectionChanged += pivot_SelectionChanged;
                UpdateApplicationBar(pivot);
            }
            else
            {
	            var panorama = this.GetVisualAncestors<Panorama>().FirstOrDefault();
	            if (panorama != null)
	            {
		            inPivotOrPano = true;

		            panorama.SelectionChanged -= panorama_SelectionChanged;
		            panorama.SelectionChanged += panorama_SelectionChanged;
	            }
            }

			if (!inPivotOrPano && page != null)
            {
                page.ApplicationBar = applicationBar;
                Build();
            }

			SubscribeToApplicationBarEvents(applicationBar);

			PhoneApplicationService.Current.Deactivated -= HandleAppDeactivated;
			PhoneApplicationService.Current.Deactivated += HandleAppDeactivated;
        }

	    void HandleAppDeactivated(object sender, DeactivatedEventArgs e)
	    {
		    IsMenuVisible = false;
	    }

	    void HandleUnloaded(object sender, RoutedEventArgs e)
		{
			Detach();

			loaded = false;
		}

		void Detach()
		{
			var pivot = this.GetVisualAncestors<Pivot>().FirstOrDefault();
			if (pivot != null)
			{
				pivot.SelectionChanged -= pivot_SelectionChanged;
			}

			var panorama = this.GetVisualAncestors<Panorama>().FirstOrDefault();
			if (panorama != null)
			{
				panorama.SelectionChanged -= panorama_SelectionChanged;
			}

			UnsubscribeToApplicationBarEvents(applicationBar);

			applicationBar.Buttons.Clear();
			applicationBar.MenuItems.Clear();

			foreach (FrameworkElement item in Items.OfType<FrameworkElement>())
			{
				UnregisterForVisibilityNotification(item);
			}

			PhoneApplicationService.Current.Deactivated -= HandleAppDeactivated;
		}

	    void SubscribeToApplicationBarEvents(ApplicationBar bar)
	    {
		    UnsubscribeToApplicationBarEvents(bar);
			bar.StateChanged += HandleApplicationBarStateChanged;
	    }

		void UnsubscribeToApplicationBarEvents(ApplicationBar bar)
		{
			bar.StateChanged -= HandleApplicationBarStateChanged;
		}

	    void HandleApplicationBarStateChanged(object sender, ApplicationBarStateChangedEventArgs args)
	    {
		    bool newValue = args.IsMenuVisible;
		    bool currentValue = IsMenuVisible;

			if (currentValue != newValue)
			{
				IsMenuVisible = newValue;
			}

			StateChanged?.Invoke(this, args);
	    }

	    void pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            UpdateApplicationBar((Pivot)sender);
        }

		void panorama_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			UpdateApplicationBar((Panorama)sender);
		}

        protected virtual void UpdateApplicationBar(Pivot pivot)
        {
            var page = this.GetVisualAncestors<PhoneApplicationPage>().LastOrDefault();

            if (pivot == null || page == null)
            {
                return;
            }

            bool assignBar = false;

            if (pivot.SelectedItem == DataContext)
            {
                assignBar = true;
            }
            else
            {
                var frameworkElement = pivot.SelectedItem as FrameworkElement;
                if (frameworkElement != null
                        && frameworkElement.DataContext == DataContext)
                {
                    assignBar = true;
                }
            }

            if (assignBar)
            {
                page.ApplicationBar = applicationBar;
                Build();
            }
            else if (page.ApplicationBar == applicationBar)
            {
                page.ApplicationBar = null;
            }
        }

	    protected virtual void UpdateApplicationBar(Panorama panorama)
	    {
			var page = this.GetVisualAncestors<PhoneApplicationPage>().LastOrDefault();

			if (panorama == null || page == null)
			{
				return;
			}

			bool assignBar = false;

			if (panorama.SelectedItem == DataContext)
			{
				assignBar = true;
			}
			else
			{
				var panoramaItem = panorama.SelectedItem as PanoramaItem;
				if (panoramaItem != null)
				{
					var childElement = panoramaItem.Content as FrameworkElement;
					if (childElement != null && childElement.DataContext == DataContext)
					{
						assignBar = true;
					}
				}

				if (!assignBar)
				{
					var frameworkElement = panorama.SelectedItem as FrameworkElement;
					if (frameworkElement != null
					    && frameworkElement.DataContext == DataContext)
					{
						assignBar = true;
					}
				}
			}

			if (assignBar)
			{
				page.ApplicationBar = applicationBar;
				Build();
			}
			else if (page.ApplicationBar == applicationBar)
			{
				page.ApplicationBar = null;
			}
	    }

	    #region AutoAttach

        public static DependencyProperty AutoAttachProperty
            = DependencyProperty.Register(
                "AutoAttach",
                typeof(bool),
                typeof(AppBar),
                new PropertyMetadata(true));

        public bool AutoAttach
        {
            get
            {
                return (bool)GetValue(AutoAttachProperty);
            }
            set
            {
                SetValue(AutoAttachProperty, value);
            }
        }

        #endregion

        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);
            if (loaded)
            {
                Build();
            }
        }

        bool building;

        void Build()
        {
            if (building)
            {
                return;
            }

            try
            {
                building = true;
                BuildAux();
            }
            finally
            {
                building = false;
            }
        }

        void BuildAux()
        {
            applicationBar.Buttons.Clear();
            applicationBar.MenuItems.Clear();

            foreach (FrameworkElement item in Items.OfType<FrameworkElement>())
            {
                RegisterVisibilityNotification("Visibility", item);
            }

	        const int maxButtons = 4;
	        int buttonCount = 0;

	        foreach (object item in Items)
	        {
		        var uiElement = item as UIElement;
				if (uiElement != null && uiElement.Visibility != Visibility.Visible)
				{
					continue;
				}

		        IApplicationBarIconButtonProvider buttonProvider 
					= item as IApplicationBarIconButtonProvider;
				if (buttonProvider != null && ++buttonCount <= maxButtons)
				{
					applicationBar.Buttons.Add(buttonProvider.Button);
				}

		        var menuItemProvider = item as IApplicationBarMenuItemProvider;

				if (menuItemProvider != null)
				{
					applicationBar.MenuItems.Add(menuItemProvider.MenuItem);
				}
	        }

			//var buttonList = (from button in Items.Where(notUIElementOrVisibleFunc)
			//                            .OfType<IApplicationBarIconButtonProvider>()
			//                    select button.Button).ToList();
			//
			//if (buttonList.Count > 4)
			//{
			//    buttonList = buttonList.Take(4).ToList();
			//}

            //applicationBar.Buttons.AddRange(buttonList);

			//var menuItemList = (from item
			//                        in Items.Where(notUIElementOrVisibleFunc)
			//                            .OfType<IApplicationBarMenuItemProvider>()
			//                    select item.MenuItem).ToList();
			//
			//applicationBar.MenuItems.AddRange(menuItemList);
        }

        readonly Func<object, bool> notUIElementOrVisibleFunc
            = x =>
            {
                var element = x as UIElement;
                return element == null || element.Visibility == Visibility.Visible;
            };

        #region ItemVisibility

		readonly static DependencyProperty ItemVisibilityProperty 
			= DependencyProperty.RegisterAttached(
				"ItemVisibility",
				typeof(Visibility),
				typeof(UserControl),
				new PropertyMetadata(HandleItemVisibilityChanged));

	    static void HandleItemVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
	    {
		    FrameworkElement element = (FrameworkElement)d;
			AppBar appBar = element.FindAncestor<AppBar>();
			if (appBar == null)
			{
				return;
			}

			if (appBar.loaded)
            {
				appBar.Build();
            }
        }

        public Visibility ItemVisibility
        {
            get
            {
                return (Visibility)GetValue(ItemVisibilityProperty);
            }
            set
            {
                SetValue(ItemVisibilityProperty, value);
            }
        }

        #endregion

        void RegisterVisibilityNotification(
							string propertyName,
							FrameworkElement element)
        {
            var expression = element.GetBindingExpression(ItemVisibilityProperty);
            if (expression == null)
            {
                Binding binding = new Binding(propertyName) { Source = element };
                element.SetBinding(ItemVisibilityProperty, binding);
            }
        }

		void UnregisterForVisibilityNotification(FrameworkElement element)
		{
			var expression = element.GetBindingExpression(ItemVisibilityProperty);
			if (expression != null)
			{
				element.ClearValue(ItemVisibilityProperty);
				//element.SetBinding(ItemVisibilityProperty, null);
			}
		}

        //		static void RegisterForNotification(
        //			string propertyName,
        //			FrameworkElement element,
        //			PropertyChangedCallback callback)
        //		{
        //			Binding binding = new Binding(propertyName) { Source = element };
        //			var property = DependencyProperty.RegisterAttached(
        //								"ChangeListenerAttached" + propertyName,
        //								typeof(object),
        //								typeof(UserControl),
        //								new PropertyMetadata(callback));
        //
        //			element.SetBinding(property, binding);
        //		}

        //		void OnItemVisibilityChanged(
        //			DependencyObject d, DependencyPropertyChangedEventArgs e)
        //		{
        //			if (loaded)
        //			{
        //				Build();
        //			}
        //		}

        public IList Buttons
        {
            get
            {
                return Items;
            }
        }

        public IList MenuItems
        {
            get
            {
                return Items;
            }
        }

		public event EventHandler<ApplicationBarStateChangedEventArgs> StateChanged = delegate { };
		
        #region IsMenuEnabled

        public static readonly DependencyProperty IsMenuEnabledProperty
            = DependencyProperty.RegisterAttached(
                "IsMenuEnabled",
                typeof(bool),
                typeof(AppBar),
                new PropertyMetadata(true, HandleMenuEnabledChanged));

        static void HandleMenuEnabledChanged(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            AppBar bar;
            if (e.NewValue != e.OldValue && (bar = d as AppBar) != null)
            {
                bar.applicationBar.IsMenuEnabled = (bool)e.NewValue;
            }
        }

        public bool IsMenuEnabled
        {
            get
            {
                return (bool)GetValue(IsMenuEnabledProperty);
            }
            set
            {
                SetValue(IsMenuEnabledProperty, value);
            }
        }

        #endregion

		#region IsMenuVisible

		public static readonly DependencyProperty IsMenuVisibleProperty
			= DependencyProperty.RegisterAttached(
				"IsMenuVisible",
				typeof(bool),
				typeof(AppBar),
				new PropertyMetadata(true, HandleMenuVisibleChanged));

		static void HandleMenuVisibleChanged(
			DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			AppBar bar;
			if (e.NewValue != e.OldValue && (bar = d as AppBar) != null)
			{
				//bar.applicationBar.IsMenuVisible = (bool)e.NewValue;
			}
		}

		public bool IsMenuVisible
		{
			get
			{
				return (bool)GetValue(IsMenuVisibleProperty);
			}
			set
			{
				SetValue(IsMenuVisibleProperty, value);
			}
		}

		#endregion

        #region IsVisible

        public static readonly DependencyProperty IsVisibleProperty
            = DependencyProperty.RegisterAttached(
                "IsVisible",
                typeof(bool),
                typeof(AppBar),
                new PropertyMetadata(true, HandleIsVisibleChanged));

        static void HandleIsVisibleChanged(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            AppBar bar;
            if (e.NewValue != e.OldValue
                && (bar = d as AppBar) != null)
            {
                bar.applicationBar.IsVisible = (bool)e.NewValue;
            }
        }

        public bool IsVisible
        {
            get
            {
                return (bool)GetValue(IsVisibleProperty);
            }
            set
            {
                SetValue(IsVisibleProperty, value);
            }
        }

        #endregion

        #region BarOpacity

        public static DependencyProperty BarOpacityProperty
            = DependencyProperty.Register(
                "BarOpacity",
                typeof(double),
                typeof(AppBar),
                new PropertyMetadata(1.0, HandleBarOpacityChanged));

        static void HandleBarOpacityChanged(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var appBar = (AppBar)d;
            appBar.applicationBar.Opacity = (double)e.NewValue;
        }

        public double BarOpacity
        {
            get
            {
                return (double)GetValue(BarOpacityProperty);
            }
            set
            {
                SetValue(BarOpacityProperty, value);
            }
        }

        #endregion

        #region BackgroundColor

        public static DependencyProperty BackgroundColorProperty
            = DependencyProperty.Register(
                "BackgroundColor",
                typeof(Color),
                typeof(AppBar),
                new PropertyMetadata(HandleBackgroundColorChanged));

        static void HandleBackgroundColorChanged(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var appBar = (AppBar)d;
	        if (appBar.applicationBar != null)
	        {
		        appBar.applicationBar.BackgroundColor = (Color)e.NewValue;
	        }
	        else
	        {
				/* If the applicationBar is null, then wait for initialization. */
		        Deployment.Current.Dispatcher.BeginInvoke(delegate
		        {
					if (appBar.applicationBar != null)
					{
						appBar.applicationBar.BackgroundColor = (Color)e.NewValue;
					}
		        });
	        }
        }

        public Color BackgroundColor
        {
            get
            {
                return (Color)GetValue(BackgroundColorProperty);
            }
            set
            {
                SetValue(BackgroundColorProperty, value);
            }
        }

        #endregion

        #region ForegroundColor

        public static DependencyProperty ForegroundColorProperty
            = DependencyProperty.Register(
                "ForegroundColor",
                typeof(Color),
                typeof(AppBar),
                new PropertyMetadata(HandleForegroundColorChanged));

        static void HandleForegroundColorChanged(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var appBar = (AppBar)d;
            appBar.applicationBar.ForegroundColor = (Color)e.NewValue;
        }

        public Color ForegroundColor
        {
            get
            {
                return (Color)GetValue(ForegroundColorProperty);
            }
            set
            {
                SetValue(ForegroundColorProperty, value);
            }
        }

        #endregion

        #region Mode

        public static DependencyProperty ModeProperty
            = DependencyProperty.Register(
                "Mode",
                typeof(ApplicationBarMode),
                typeof(AppBar),
                new PropertyMetadata(ApplicationBarMode.Default, HandleModeChanged));

        static void HandleModeChanged(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var appBar = (AppBar)d;
            appBar.applicationBar.Mode = (ApplicationBarMode)e.NewValue;
        }

        public ApplicationBarMode Mode
        {
            get
            {
                return (ApplicationBarMode)GetValue(ModeProperty);
            }
            set
            {
                SetValue(ModeProperty, value);
            }
        }

        #endregion

        public double DefaultSize
        {
            get
            {
                return applicationBar != null ? applicationBar.DefaultSize : 72.0;
            }
        }

        public double MiniSize
        {
            get
            {
                return applicationBar != null ? applicationBar.MiniSize : 30.0;
            }
        }
    }
}
